package org.g4studio.core.mvc.xstruts.actions;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.g4studio.core.mvc.xstruts.action.ActionForm;
import org.g4studio.core.mvc.xstruts.action.ActionForward;
import org.g4studio.core.mvc.xstruts.action.ActionMapping;

/**
 * This is an abstract base class that minimizes the amount of special coding
 * that needs to be written to download a file. All that is required to use this
 * class is to extend it and implement the <code>getStreamInfo()</code> method
 * so that it returns the relevant information for the file (or other stream) to
 * be downloaded. Optionally, the <code>getBufferSize()</code> method may be
 * overridden to customize the size of the buffer used to transfer the file.
 * 
 * @since Struts 1.2.6
 */
public abstract class DownloadAction extends BaseAction {
	/**
	 * If the <code>getBufferSize()</code> method is not overridden, this is the
	 * buffer size that will be used to transfer the data to the servlet output
	 * stream.
	 */
	protected static final int DEFAULT_BUFFER_SIZE = 4096;

	/**
	 * Returns the information on the file, or other stream, to be downloaded by
	 * this action. This method must be implemented by an extending class.
	 * 
	 * @param mapping
	 *            The ActionMapping used to select this instance.
	 * @param form
	 *            The optional ActionForm bean for this request (if any).
	 * @param request
	 *            The HTTP request we are processing.
	 * @param response
	 *            The HTTP response we are creating.
	 * @return The information for the file to be downloaded.
	 * @throws Exception
	 *             if an exception occurs.
	 */
	protected abstract StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form, HttpServletRequest request,
			HttpServletResponse response) throws Exception;

	/**
	 * Returns the size of the buffer to be used in transferring the data to the
	 * servlet output stream. This method may be overridden by an extending
	 * class in order to customize the buffer size.
	 * 
	 * @return The size of the transfer buffer, in bytes.
	 */
	protected int getBufferSize() {
		return DEFAULT_BUFFER_SIZE;
	}

	/**
	 * Process the specified HTTP request, and create the corresponding HTTP
	 * response (or forward to another web component that will create it).
	 * Return an <code>ActionForward</code> instance describing where and how
	 * control should be forwarded, or <code>null</code> if the response has
	 * already been completed.
	 * 
	 * @param mapping
	 *            The ActionMapping used to select this instance.
	 * @param form
	 *            The optional ActionForm bean for this request (if any).
	 * @param request
	 *            The HTTP request we are processing.
	 * @param response
	 *            The HTTP response we are creating.
	 * @return The forward to which control should be transferred, or
	 *         <code>null</code> if the response has been completed.
	 * @throws Exception
	 *             if an exception occurs.
	 */
	public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		StreamInfo info = getStreamInfo(mapping, form, request, response);
		String contentType = info.getContentType();
		InputStream stream = info.getInputStream();

		try {
			response.setContentType(contentType);
			copy(stream, response.getOutputStream());
		} finally {
			if (stream != null) {
				stream.close();
			}
		}

		// Tell Struts that we are done with the response.
		return null;
	}

	/**
	 * Copy bytes from an <code>InputStream</code> to an
	 * <code>OutputStream</code>.
	 * 
	 * @param input
	 *            The <code>InputStream</code> to read from.
	 * @param output
	 *            The <code>OutputStream</code> to write to.
	 * @return the number of bytes copied
	 * @throws IOException
	 *             In case of an I/O problem
	 */
	public int copy(InputStream input, OutputStream output) throws IOException {
		byte[] buffer = new byte[getBufferSize()];
		int count = 0;
		int n = 0;

		while (-1 != (n = input.read(buffer))) {
			output.write(buffer, 0, n);
			count += n;
		}

		return count;
	}

	/**
	 * The information on a file, or other stream, to be downloaded by the
	 * <code>DownloadAction</code>.
	 */
	public static interface StreamInfo {
		/**
		 * Returns the content type of the stream to be downloaded.
		 * 
		 * @return The content type of the stream.
		 */
		String getContentType();

		/**
		 * Returns an input stream on the content to be downloaded. This stream
		 * will be closed by the <code>DownloadAction</code>.
		 * 
		 * @return The input stream for the content to be downloaded.
		 * @throws IOException
		 *             if an error occurs
		 */
		InputStream getInputStream() throws IOException;
	}

	/**
	 * A concrete implementation of the <code>StreamInfo</code> interface which
	 * simplifies the downloading of a file from the disk.
	 */
	public static class FileStreamInfo implements StreamInfo {
		/**
		 * The content type for this stream.
		 */
		private String contentType;

		/**
		 * The file to be downloaded.
		 */
		private File file;

		/**
		 * Constructs an instance of this class, based on the supplied
		 * parameters.
		 * 
		 * @param contentType
		 *            The content type of the file.
		 * @param file
		 *            The file to be downloaded.
		 */
		public FileStreamInfo(String contentType, File file) {
			this.contentType = contentType;
			this.file = file;
		}

		/**
		 * Returns the content type of the stream to be downloaded.
		 * 
		 * @return The content type of the stream.
		 */
		public String getContentType() {
			return this.contentType;
		}

		/**
		 * Returns an input stream on the file to be downloaded. This stream
		 * will be closed by the <code>DownloadAction</code>.
		 * 
		 * @return The input stream for the file to be downloaded.
		 * @throws IOException
		 *             if an error occurs
		 */
		public InputStream getInputStream() throws IOException {
			FileInputStream fis = new FileInputStream(file);
			BufferedInputStream bis = new BufferedInputStream(fis);

			return bis;
		}
	}

	/**
	 * A concrete implementation of the <code>StreamInfo</code> interface which
	 * simplifies the downloading of a web application resource.
	 */
	public static class ResourceStreamInfo implements StreamInfo {
		/**
		 * The content type for this stream.
		 */
		private String contentType;

		/**
		 * The servlet context for the resource to be downloaded.
		 */
		private ServletContext context;

		/**
		 * The path to the resource to be downloaded.
		 */
		private String path;

		/**
		 * Constructs an instance of this class, based on the supplied
		 * parameters.
		 * 
		 * @param contentType
		 *            The content type of the file.
		 * @param context
		 *            The servlet context for the resource.
		 * @param path
		 *            The path to the resource to be downloaded.
		 */
		public ResourceStreamInfo(String contentType, ServletContext context, String path) {
			this.contentType = contentType;
			this.context = context;
			this.path = path;
		}

		/**
		 * Returns the content type of the stream to be downloaded.
		 * 
		 * @return The content type of the stream.
		 */
		public String getContentType() {
			return this.contentType;
		}

		/**
		 * Returns an input stream on the resource to be downloaded. This stream
		 * will be closed by the <code>DownloadAction</code>.
		 * 
		 * @return The input stream for the resource to be downloaded.
		 * @throws IOException
		 *             if an error occurs
		 */
		public InputStream getInputStream() throws IOException {
			return context.getResourceAsStream(path);
		}
	}
}
